﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Media.Animation;
using GraphosData;
using System.Collections.ObjectModel;
using System.Windows.Threading;
using System.ComponentModel;

namespace GraphosControls
{

    /// <summary>
    /// Interaction logic for GraphViewer.xaml
    /// </summary>
    public partial class GraphViewer : UserControl
    {
        public GraphViewer()
        {
            InitializeComponent();
            DataContext = this;
        }

        #region Dependecy Properties

        /// <summary>
        /// Gets or sets the current selected node
        /// </summary>
        public Node RootNode
        {
            get { return (Node)GetValue(SelectedNodeProperty); }
            set { SetValue(SelectedNodeProperty, value); }
        }
        public static readonly DependencyProperty SelectedNodeProperty =
                  DependencyProperty.Register("RootNode", typeof(Node), typeof(GraphViewer),
                  new FrameworkPropertyMetadata(
                      null,
                      FrameworkPropertyMetadataOptions.None,
                      new PropertyChangedCallback(OnSelectedNodeChanged),
                      new CoerceValueCallback(CoerceRootNode)));
        private static void OnSelectedNodeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            // update the graph with the appropriate nodes and position them
            GraphViewer g = sender as GraphViewer;
            VisualNode o;
            if (args.OldValue != null && g.m_nodeToVisualNode.TryGetValue(args.OldValue as Node, out o))
                o.IsRootNode = false;

            VisualNode n;
            if (g.TryAddVisualNode(args.NewValue as Node, out n))
            {
                n.Position = new Point(Rand.Double * g.MainCanvas.ActualWidth, Rand.Double * g.MainCanvas.ActualHeight);
            }
            n.IsRootNode = true;

            g.CalculateNodePositions(n);
            g.AnimateScrollToCenter();

            // do a big zoom on the first node viewed
            if (args.OldValue == null)
                g.AnimateZoom(g.Zoom, 1.0);
        }
        private static object CoerceRootNode(DependencyObject sender, object value)
        {
            // special logic when the value tries to be null
            // TODO: this would need to changed if another graph were to be loaded at some point
            GraphViewer viewer = sender as GraphViewer;
            if (value == null)
                return viewer.RootNode;
            return value;

        }

        /// <summary>
        /// Gets or set the current Graph object which populates the nodes
        /// </summary>
        public Graph Graph
        {
            get { return (Graph)GetValue(GraphProperty); }
            set { SetValue(GraphProperty, value); }
        }
        public static readonly DependencyProperty GraphProperty =
                  DependencyProperty.Register("Graph", typeof(Graph), typeof(GraphViewer),
                  new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None));

        /// <summary>
        /// Gets of sets the current PositionUtility for this instance
        /// </summary>
        public PositionUtility Pos
        {
            get { return (PositionUtility)GetValue(PosProperty); }
            set { SetValue(PosProperty, value); }
        }
        public static readonly DependencyProperty PosProperty =
            DependencyProperty.Register("Pos", typeof(PositionUtility), typeof(GraphViewer),
            new FrameworkPropertyMetadata(new PositionUtility(), FrameworkPropertyMetadataOptions.None));

        /// <summary>
        /// Gets or sets the current TransitionUtility for this instance
        /// </summary>
        public TransitionUtility Trans
        {
            get { return (TransitionUtility)GetValue(TransProperty); }
            set { SetValue(TransProperty, value); }
        }
        public static readonly DependencyProperty TransProperty =
            DependencyProperty.Register("Trans", typeof(TransitionUtility), typeof(GraphViewer),
            new FrameworkPropertyMetadata(new TransitionUtility(), FrameworkPropertyMetadataOptions.None));

        /// <summary>
        /// Gets or sets the current scroll point as a percentage
        /// (ie, when ScrollPosition == (0.5, 0.5) the ScrollViewer is centered)
        /// </summary>
        public Point ScrollPosition
        {
            get { return (Point)GetValue(ScrollPositionProperty); }
            set { SetValue(ScrollPositionProperty, value); }
        }
        public static readonly DependencyProperty ScrollPositionProperty =
            DependencyProperty.Register("ScrollPosition", typeof(Point), typeof(GraphViewer),
            new FrameworkPropertyMetadata(
                new Point(),
                FrameworkPropertyMetadataOptions.None,
                new PropertyChangedCallback(OnScrollPositionChanged)));
        private static void OnScrollPositionChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            GraphViewer viewer = sender as GraphViewer;
            Point p = (Point)args.NewValue;
            viewer.MainScrollViewer.ScrollToHorizontalOffset(
                viewer.MainScrollViewer.ExtentWidth * p.X - viewer.MainScrollViewer.ViewportWidth / 2);
            viewer.MainScrollViewer.ScrollToVerticalOffset(
                viewer.MainScrollViewer.ExtentHeight * p.Y - viewer.MainScrollViewer.ViewportHeight / 2);

        }

        /// <summary>
        /// Gets or sets the Zoom level of the graph viewer
        /// </summary>
        public double Zoom
        {
            get { return (double)GetValue(ZoomProperty); }
            set { SetValue(ZoomProperty, value); }
        }
        public static readonly DependencyProperty ZoomProperty =
            DependencyProperty.Register("Zoom", typeof(double), typeof(GraphViewer),
            new FrameworkPropertyMetadata(
                0.0,
                FrameworkPropertyMetadataOptions.None,
                new PropertyChangedCallback(OnZoomChanged)));
        private static void OnZoomChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            // apply the scale transform to the canvas when the zoom is changed
            GraphViewer viewer = sender as GraphViewer;
            double value = (double)args.NewValue;
            viewer.UpdateScrollSize();
            viewer.MainCanvas.RenderTransform = new ScaleTransform(value, value);
        }

        public IList<Relation> GroupRelations
        {
            get { return (IList<Relation>)GetValue(GroupRelationsProperty); }
            set { SetValue(GroupRelationsProperty, value); }
        }
        public static readonly DependencyProperty GroupRelationsProperty =
            DependencyProperty.Register("GroupRelations", typeof(IList<Relation>), typeof(GraphViewer),
            new FrameworkPropertyMetadata(new List<Relation>(), FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(OnGroupRelationsChanged)));
        private static void OnGroupRelationsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            // NOTE: this is a hack
            GraphViewer viewer = sender as GraphViewer;
            ListBox lb = (ListBox)Application.Current.MainWindow.FindName("GroupListBox");
            lb.UnselectAll();
        }

        public IList<Relation> CurrentSelectedRelations
        {
            get { return (IList<Relation>)GetValue(CurrentSelectedRelationsProperty); }
            set { SetValue(CurrentSelectedRelationsProperty, value); }
        }
        public static readonly DependencyProperty CurrentSelectedRelationsProperty =
            DependencyProperty.Register("CurrentSelectedRelations", typeof(IList<Relation>), typeof(GraphViewer),
            new FrameworkPropertyMetadata(new List<Relation>(), FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(OnCurrentSelectedRelationsChanged), new CoerceValueCallback(CoerceCurrentSelectedRelations)));

        private static void OnCurrentSelectedRelationsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            GraphViewer viewer = sender as GraphViewer;
            var items = (IList<Relation>)args.NewValue;
            viewer.CalculateNodePositionWithGroups(null);
        }
        private static object CoerceCurrentSelectedRelations(DependencyObject sender, object value)
        {

            if ((value as IList<Relation>).Count == 0 && (sender as GraphViewer).GroupRelations.Count > 0)
                return (sender as GraphViewer).GroupRelations;
            return value;
        }



        #endregion

        #region Overrides

        protected override void OnPreviewMouseWheel(MouseWheelEventArgs e)
        {
            // Zoom slider.
            if (e.Delta > 0)
                Zoom = Math.Min(Zoom * 1.05, 100);
            else
                Zoom = Math.Max(Zoom / 1.05, .1);

            Pos.AllowAutoZoom = false;
            BeginAnimation(ZoomProperty, null);

            e.Handled = true;
            base.OnPreviewMouseWheel(e);
        }
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            StartScoll(e);

            base.OnPreviewMouseLeftButtonDown(e);
        }

        protected override void OnMouseDown(MouseButtonEventArgs e)
        {
            if (e.MiddleButton == MouseButtonState.Pressed)
            {
                StartScoll(e);
            }

            base.OnMouseDown(e);
        }

        protected override void OnMouseUp(MouseButtonEventArgs e)
        {
            // logic for releasing manual scroll
            if (IsMouseCaptured)
            {
                Cursor = Cursors.Arrow;
                ReleaseMouseCapture();
            }
            base.OnMouseUp(e);
        }

        protected override void OnMouseRightButtonUp(MouseButtonEventArgs e)
        {
            // logic for releasing all dragging nodes
            if (Pos.DragNode != null && Pos.DragNode.IsMouseCaptured)
            {
                Pos.DragPosition = e.GetPosition(MainCanvas);
                Pos.DragNode = null;
            }
            base.OnMouseRightButtonUp(e);
        }
        protected override void OnPreviewMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            if (IsMouseCaptured)
            {
                // go ahead and scroll the canvas
                // NOTE: most of the scrolling and zooming code was borrowed/modified from the Family.Show application
                Point newPoint = e.GetPosition(this);

                double dx = newPoint.X > m_startScrollPoint.X ? -(newPoint.X - m_startScrollPoint.X) : m_startScrollPoint.X - newPoint.X;
                double dy = newPoint.Y > m_startScrollPoint.Y ? -(newPoint.Y - m_startScrollPoint.Y) : m_startScrollPoint.Y - newPoint.Y;

                MainScrollViewer.ScrollToHorizontalOffset(m_startScrollOffset.X + dx);
                MainScrollViewer.ScrollToVerticalOffset(m_startScrollOffset.Y + dy);
            }
            else if (e.RightButton == MouseButtonState.Pressed && Pos.DragNode != null)
            {
                // changed the drag position of the drag node if need be
                Pos.DragPosition = e.GetPosition(MainCanvas);
            }

        }

        #endregion

        #region Events Handlers

        protected override void OnInitialized(EventArgs e)
        {
            // set up event handlers
            MainCanvas.Loaded += new RoutedEventHandler(MainCanvas_Loaded);
            MainCanvas.SizeChanged += MainCanvas_SizeChanged;

            MainScrollViewer.ScrollChanged += new ScrollChangedEventHandler(MainScrollViewer_ScrollChanged);
            Pos.PositionAnimationComplete += PositionUtility_AnimationComplete;
            Pos.ZoomRequested += PositionUtility_ZoomRequested;
            Trans.TransitionAnimationComplete += TransitionUtility_AnimationComplete;

            base.OnInitialized(e);
        }

        private void StartScoll(MouseButtonEventArgs e)
        {
            // logic to decide if the graph should start the manual scroll process
            if (IsMouseOver)
            {
                m_startScrollPoint = e.GetPosition(this);
                m_startScrollOffset.X = MainScrollViewer.HorizontalOffset;
                m_startScrollOffset.Y = MainScrollViewer.VerticalOffset;

                Cursor = Cursors.ScrollAll;
                CaptureMouse();
            }
        }

        private void MainCanvas_Loaded(object sender, RoutedEventArgs e)
        {
            // update the scroll size
            UpdateScrollSize();
        }
        private void MainCanvas_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (e.NewSize == e.PreviousSize)
                return;

            // update canvas and center it using animation
            UpdateScrollSize();
            AnimateScrollToCenter();

            if (RootNode != null)
            {
                // reposition nodes using the new size (with auto zoom property set to true)
                Pos.Size = new Size(e.NewSize.Width, e.NewSize.Height);
                Pos.AllowAutoZoom = true;
                Pos.Restart();
            }
        }
        private void MainScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            // NOTE: taken from Family.Show

            //adjust the scroll position when the extent width or height changed
            if (e.ExtentWidthChange != 0 && e.ExtentWidthChange != e.ExtentWidth)
            {
                // Keep centered horizontaly.
                double percent = e.ExtentWidthChange / (e.ExtentWidth - e.ExtentWidthChange);
                double middle = e.HorizontalOffset + (e.ViewportWidth / 2);
                MainScrollViewer.ScrollToHorizontalOffset(e.HorizontalOffset + (middle * percent));
            }

            if (e.ExtentHeightChange != 0 &&
                e.ExtentHeightChange != e.ExtentHeight)
            {
                // Keep centered verically.
                double percent = e.ExtentHeightChange / (e.ExtentHeight - e.ExtentHeightChange);
                double middle = e.VerticalOffset + (e.ViewportHeight / 2);
                MainScrollViewer.ScrollToVerticalOffset(e.VerticalOffset + (middle * percent));
            }
        }


        private void VisualNode_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            // a visual node was clicked
            // make it the root node
            VisualNode vn = sender as VisualNode;
            if (vn == null)
                return;

            RootNode = vn.Node;
            e.Handled = true;
        }
        private void VisualNode_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            // a visual node was right clicked
            // make it the drag node and set the drag position
            VisualNode vn = sender as VisualNode;
            if (vn == null)
                return;
            foreach (VisualEdge edge in m_allEdges)
            {
                edge.LabelTextBlock.BitmapEffect = null;
            }
            Pos.DragNode = vn;
            Pos.DragPosition = e.GetPosition(MainCanvas);
            e.Handled = true;
        }
        private void VisualNode_MouseEnter(object sender, MouseEventArgs e)
        {

            // when a node is moused over
            // highlight its edges and display the labels
            // TODO : This is having some buggy behavior
            //        (ie, sometimes edges start off highlighted, and you must mouse over 
            //        and then away again for it to turn off), not sure the best fix
            VisualNode node = sender as VisualNode;
            if (node.IsRootNode)
            {
                foreach (VisualEdge edge in m_visualNodesToVisualEdge[node].Values)
                {
                    //if (!MainCanvas.Children.Contains(edge.LabelTextBlock))
                    //{
                    //    MainCanvas.Children.Add(edge.LabelTextBlock);
                    //    m_edgeLabels.Add(edge.LabelTextBlock);
                    //}
                    //TransitionUtility.ApplyEdgeHighlightAnimation(edge);
                    TransitionUtility.ApplyGlowAnimation(edge.LabelTextBlock, (edge.DesiredStrokeBrush as SolidColorBrush).Color);
                }
            }
            else if (m_visualNodesToVisualEdge.ContainsKey(node))
            {
                foreach (VisualEdge edge in m_visualNodesToVisualEdge[node].Values)
                {
                    if (!MainCanvas.Children.Contains(edge.LabelTextBlock))
                    {
                        MainCanvas.Children.Add(edge.LabelTextBlock);
                        m_edgeLabels.Add(edge.LabelTextBlock);
                    }
                    //TransitionUtility.ApplyEdgeHighlightAnimation(edge);
                    TransitionUtility.ApplyGlowAnimation(edge.LabelTextBlock, (edge.DesiredStrokeBrush as SolidColorBrush).Color);
                    TransitionUtility.ApplyLabelFadeInAnimation(edge.LabelTextBlock);
                }
            }
        }
        private void VisualNode_MouseLeave(object sender, MouseEventArgs e)
        {

            // unhighlight a nodes edges when the mouse leaves the focus area
            // TODO : see the notes on VisualNode_MouseEnter
            VisualNode node = sender as VisualNode;

            if (node.IsRootNode)
            {
                foreach (VisualEdge edge in m_visualNodesToVisualEdge[node].Values)
                {
                    //Trans.ApplyDestroyLabelAnimation(edge.LabelTextBlock);
                    TransitionUtility.ApplyUnGlowAnimation(edge.LabelTextBlock);
                    //TransitionUtility.ApplyEdgeResetAnimation(edge);
                }
            }
            else if (m_visualNodesToVisualEdge.ContainsKey(node))
            {
                foreach (VisualEdge edge in m_visualNodesToVisualEdge[node].Values)
                {
                    //Trans.ApplyDestroyLabelAnimation(edge.LabelTextBlock);
                    //TransitionUtility.ApplyEdgeResetAnimation(edge);
                    TransitionUtility.ApplyUnGlowAnimation(edge.LabelTextBlock);
                    TransitionUtility.ApplyLabelFadeOutAnimation(edge.LabelTextBlock);
                }
            }
        }

        private void PositionUtility_AnimationComplete(object sender, RoutedEventArgs e)
        {
            // add all the edge labels from the root node
            // TODO: this can be modified to be all labels for all edges...but it gets bit crowded on screen
            Pos.AllowAutoZoom = false;
            Pos.ZoomRequested -= PositionUtility_ZoomRequested;

            VisualNode root = m_nodeToVisualNode[RootNode];
            foreach (var edge in m_visualNodesToVisualEdge[root].Values)
            {

                //TryAddEdgeLabel(edge.NewLabelTextBlock);
                if (!MainCanvas.Children.Contains(edge.LabelTextBlock))
                {
                    MainCanvas.Children.Add(edge.LabelTextBlock);
                    m_edgeLabels.Add(edge.LabelTextBlock);
                    TransitionUtility.ApplyCreateLabelAnimation(edge.LabelTextBlock);
                }
                else if (!m_edgeLabels.Contains(edge.LabelTextBlock))
                {
                    // this is a special case usually when the previous label has not been disposed of
                    // but still needs to be added to the visual tree
                    // happens when a graph settles before the previous animation
                    // has finished and disposed of its visual objects
                    TextBlock tb = edge.NewLabelTextBlock;
                    MainCanvas.Children.Add(tb);
                    m_edgeLabels.Add(tb);
                    TransitionUtility.ApplyCreateLabelAnimation(tb);
                }
            }
        }
        private void PositionUtility_ZoomRequested(object sender, ZoomRequestedArgs e)
        {
            // a zoom was requested so see if it should be granted
            if (m_zoomRequestCount < 5)
            {
                m_zoomRequestCount++;
            }
            else
            {
                AnimateZoom(Zoom, e.SuggestedValue);
                m_zoomRequestCount = 0;
            }


        }
        private void TransitionUtility_AnimationComplete(object sender, TransitionAnimationCompleteEventArgs e)
        {
            // remove all visuals which are done animating from the MainCanvas
            foreach (UIElement elem in e.RemovedElements)
            {
                MainCanvas.Children.Remove(elem);
            }
        }

        #endregion

        #region Node Positioning

        /// <summary>
        /// Position the nodes using groups
        /// </summary>
        /// <param name="relations">a list of Relations which should be included in the grouping</param>
        private void CalculateNodePositionWithGroups(IEnumerable<Relation> relations)
        {
            // get visual root
            VisualNode visualRootNode = m_nodeToVisualNode[RootNode];

            // remove labels
            foreach (var label in m_edgeLabels)
            {
                Trans.ApplyDestroyLabelAnimation(label);
            }
            m_edgeLabels.Clear();

            // get the keepers
            List<VisualNode> keepers = new List<VisualNode>();

            // get the newbs (the nodes which need to be added back)
            List<Node> newbs = new List<Node>();
            foreach (var node in Graph.GetNodeRelations(RootNode, CurrentSelectedRelations))
            {
                if (m_nodeToVisualNode.ContainsKey(node))
                    keepers.Add(m_nodeToVisualNode[node]);
                else if (m_hiddenNodePositions.ContainsKey(node))
                    newbs.Add(node);
            }

            // get the tossers (nodes which need to be tossed)
            List<VisualNode> tossers = m_nodeToVisualNode.Values.Except(keepers).Where(vn => vn.Node != RootNode).ToList();

            // save the tossers' positions for when they are needed again
            foreach (var from in tossers)
            {
                foreach (var to in from.AdjacentVisualNodes)
                {
                    RemoveVisualEdge(from, to);
                }
                m_hiddenNodePositions[from.Node] = from.Position;
                RemoveVisualNode(from);
            }

            // add in the newbs
            foreach (var node in newbs)
            {
                VisualNode visualNode;
                if (TryAddVisualNode(node, out visualNode))
                {
                    visualNode.Position = m_hiddenNodePositions[node];
                }
                m_hiddenNodePositions.Remove(node);
            }

            // create the adjacency lists for the positioning algorithm
            foreach (VisualNode visualNode in m_nodeToVisualNode.Values)
            {
                List<VisualNode> listAdjacentVisualNodes = new List<VisualNode>();
                foreach (Node node in visualNode.Node.AdjacentNodes)
                {
                    VisualNode toVisualNode;
                    if (m_nodeToVisualNode.TryGetValue(node, out toVisualNode))
                    {
                        listAdjacentVisualNodes.Add(toVisualNode);
                    }
                }
                visualNode.AdjacentVisualNodes = listAdjacentVisualNodes;
            }

            // add the appropriate edges
            m_allEdges.Clear();
            foreach (var from in visualRootNode.AdjacentVisualNodes)
            {
                foreach (var to in from.AdjacentVisualNodes.Where(n => n != visualRootNode))
                {

                    VisualEdge visualEdgeTo;
                    if (from.Node.RelationOf(to.Node) < to.Node.RelationOf(from.Node))
                        TryAddVisualEdge(from, to, out visualEdgeTo);
                    else
                        TryAddVisualEdge(to, from, out visualEdgeTo);
                    m_allEdges.Add(visualEdgeTo);
                    visualEdgeTo.UpdateStyles();  // reset styles, so edges look good
                }
                VisualEdge visualEdgeFrom;
                TryAddVisualEdge(visualRootNode, from, out visualEdgeFrom);
                m_allEdges.Add(visualEdgeFrom);
                visualEdgeFrom.UpdateStyles();

            }

            // start up the animation with auto zoom
            // TODO : AutoZoom could obviously be user controlled setting at some point
            Pos.AllowAutoZoom = true;
            Pos.ZoomRequested += PositionUtility_ZoomRequested;
            Pos.Start(visualRootNode, m_allEdges,
               MainCanvas.ActualWidth, MainCanvas.ActualHeight);
        }

        /// <summary>
        /// Positions the nodes using a new root node
        /// </summary>
        /// <param name="visualRootNode">The root node which is at the center</param>
        private void CalculateNodePositions(VisualNode visualRootNode)
        {
            // TODO : it would be cool to be able to have multiple root nodes at once, and they would share all their children
            //        that we require some major surgery me thinks

            // remove the labels
            foreach (var label in m_edgeLabels)
            {
                Trans.ApplyDestroyLabelAnimation(label);
            }
            m_edgeLabels.Clear();

            // the list of nodes which need to be removed
            List<VisualNode> visualNodesToRemove = new List<VisualNode>();
            foreach (var pair in m_nodeToVisualNode)
            {
                if (!visualRootNode.Node.AdjacentNodes.Contains(pair.Key) && pair.Key != visualRootNode.Node)
                    visualNodesToRemove.Add(pair.Value);
            }


            // remove them visual edges that aren't needed anymore
            foreach (var from in visualNodesToRemove)
            {
                foreach (var to in from.AdjacentVisualNodes)
                {
                    RemoveVisualEdge(from, to);
                }
                RemoveVisualNode(from);
            }

            // add in the new visual nodes
            foreach (Node node in visualRootNode.Node.AdjacentNodes.Except(m_nodeToVisualNode.Keys))
            {
                VisualNode visualNode;
                if (TryAddVisualNode(node, out visualNode))
                {
                    // TODO initial Positioning can go here
                    visualNode.Position = new Point(Rand.Double * MainCanvas.ActualWidth, MainCanvas.ActualHeight - Rand.Double * MainCanvas.ActualHeight);
                }
            }

            // set up the visual node adjacency lists
            foreach (VisualNode visualNode in m_nodeToVisualNode.Values)
            {
                List<VisualNode> listAdjacentVisualNodes = new List<VisualNode>();
                foreach (Node node in visualNode.Node.AdjacentNodes)
                {
                    VisualNode toVisualNode;
                    if (m_nodeToVisualNode.TryGetValue(node, out toVisualNode))
                    {
                        listAdjacentVisualNodes.Add(toVisualNode);
                    }
                }
                visualNode.AdjacentVisualNodes = listAdjacentVisualNodes;
            }

            // add the appropriate edges
            m_allEdges.Clear();
            foreach (var from in visualRootNode.AdjacentVisualNodes)
            {
                foreach (var to in from.AdjacentVisualNodes.Where(n => n != visualRootNode))
                {

                    VisualEdge visualEdgeTo;
                    if (from.Node.RelationOf(to.Node) < to.Node.RelationOf(from.Node))
                        TryAddVisualEdge(from, to, out visualEdgeTo);
                    else
                        TryAddVisualEdge(to, from, out visualEdgeTo);
                    m_allEdges.Add(visualEdgeTo);
                    visualEdgeTo.UpdateStyles();
                }
                VisualEdge visualEdgeFrom;
                TryAddVisualEdge(visualRootNode, from, out visualEdgeFrom);
                m_allEdges.Add(visualEdgeFrom);
                visualEdgeFrom.UpdateStyles();
            }

            // repopulate the the edge group list box and unselect them all
            GroupRelations = Graph.RelationsOf(RootNode).ToList();

            // start up the animation with auto zoom
            // TODO : AutoZoom could obviously be user controlled setting at some point 
            Pos.AllowAutoZoom = true;
            Pos.ZoomRequested += PositionUtility_ZoomRequested;
            Pos.Start(visualRootNode, m_allEdges,
               MainCanvas.ActualWidth, MainCanvas.ActualHeight);
        }


        #endregion

        #region Private Helpers

        private void UpdateScrollSize()
        {
            // NOTE : taken from Family.Show app

            // Nothing to do if the diagram is empty.
            if (ActualWidth == 0 || ActualHeight == 0)
                return;

            Size diagramSize = new Size(
                MainCanvas.ActualWidth * Zoom,
                MainCanvas.ActualHeight * Zoom);

            // The grid contains the diagram, set the size of the grid so it's
            // large enough to allow the diagram to scroll from edge to edge.
            InnerGrid.Width = Math.Max(0, (ActualWidth * 2) + diagramSize.Width - 100);
            InnerGrid.Height = Math.Max(0, (ActualHeight * 2) + diagramSize.Height - 100);
        }
        private void AnimateZoom(double from, double to)
        {
            if (to > 1.15)
            {
                // don't auto zoom more than 1.15
                // this number is arbitrary and should be centralized somewhere else
                to = 1.15;
            }
            if (from != to)
            {
                DoubleAnimation zoom = new DoubleAnimation();
                zoom.From = from;
                zoom.To = to;
                zoom.AccelerationRatio = 0.75;
                zoom.DecelerationRatio = 0.25;
                zoom.Duration = TimeSpan.FromSeconds(Math.Max(1.0, Math.Abs((from - to) * 2)));
                zoom.Freeze();
                BeginAnimation(ZoomProperty, zoom);
            }
        }
        private void AnimateScrollToCenter()
        {
            //scroll to the center node
            PointAnimation anim = new PointAnimation();
            anim.From = GetOffSet();
            anim.To = new Point(0.5, 0.5);
            anim.AccelerationRatio = 0.75;
            anim.DecelerationRatio = 0.25;
            anim.Duration = TimeSpan.FromSeconds(Math.Max(1.0, (anim.From.Value - anim.To.Value).Length * 2));
            anim.Freeze();
            BeginAnimation(ScrollPositionProperty, anim);
        }
        private Point GetOffSet()
        {
            // get the scroll offset of the current position
            Point offset = new Point();
            if (MainScrollViewer.ExtentWidth != 0 && MainScrollViewer.ExtentHeight != 0)
            {
                offset.X = (MainScrollViewer.HorizontalOffset + (MainScrollViewer.ViewportWidth / 2)) / MainScrollViewer.ExtentWidth;
                offset.Y = (MainScrollViewer.VerticalOffset + (MainScrollViewer.ViewportHeight / 2)) / MainScrollViewer.ExtentHeight;
            }
            return offset;
        }

        // attempt to add a VisualNode to the viewer
        // visualNode will return a valid VisualNode always

        private bool TryAddVisualNode(Node node, out VisualNode visualNode)
        {
            if (!m_nodeToVisualNode.TryGetValue(node, out visualNode))
            {
                visualNode = new VisualNode(node);
                m_nodeToVisualNode.Add(node, visualNode);
                MainCanvas.Children.Add(visualNode);
                TransitionUtility.ApplyCreateNodeAnimation(visualNode);
                return true;
            }
            return false;
        }

        // remove a visualnode (this applies its destroy animation)
        private void RemoveVisualNode(VisualNode visualNode)
        {
            if (m_nodeToVisualNode.Remove(visualNode.Node))
                Trans.ApplyDestroyNodeAnimation(visualNode);
        }

        // try to add a visual edge between two visual nodes (visualEdge will always contain a non null value)
        private bool TryAddVisualEdge(VisualNode from, VisualNode to, out VisualEdge visualEdge)
        {
            if (!m_visualNodesToVisualEdge.ContainsKey(from))
            {
                m_visualNodesToVisualEdge[from] = new Dictionary<VisualNode, VisualEdge>();
            }

            if (m_visualNodesToVisualEdge.ContainsKey(to) &&
                m_visualNodesToVisualEdge[to].TryGetValue(from, out visualEdge))
            {
                visualEdge.SwitchDirection();
                m_visualNodesToVisualEdge[to].Remove(from);

                if (m_visualNodesToVisualEdge[to].Count == 0)
                    m_visualNodesToVisualEdge.Remove(to);

                m_visualNodesToVisualEdge[from][to] = visualEdge;
            }
            else if (!m_visualNodesToVisualEdge[from].TryGetValue(to, out visualEdge))
            {
                visualEdge = new VisualEdge(from, to);
                visualEdge.CenterNode = new VisualNode();
                m_visualNodesToVisualEdge[from][to] = visualEdge;
                MainCanvas.Children.Add(visualEdge);
                TransitionUtility.ApplyCreateEdgeAnimation(visualEdge);
            }


            return true;
        }

        // remove the edge from the specified visual nodes
        private void RemoveVisualEdge(VisualNode from, VisualNode to)
        {

            // remove edges which go from -- to, or to -- from
            VisualEdge visualEdge = null;
            if (m_visualNodesToVisualEdge.ContainsKey(from) &&
                    m_visualNodesToVisualEdge[from].TryGetValue(to, out visualEdge))
            {
                m_visualNodesToVisualEdge[from].Remove(to);
                if (m_visualNodesToVisualEdge[from].Count == 0)
                    m_visualNodesToVisualEdge.Remove(from);
                Trans.ApplyDestroyEdgeAnimation(visualEdge);
            }
            if (m_visualNodesToVisualEdge.ContainsKey(to) &&
                  m_visualNodesToVisualEdge[to].TryGetValue(from, out visualEdge))
            {
                m_visualNodesToVisualEdge[to].Remove(from);
                if (m_visualNodesToVisualEdge[to].Count == 0)
                    m_visualNodesToVisualEdge.Remove(to);
                Trans.ApplyDestroyEdgeAnimation(visualEdge);
            }
        }

        private bool TryAddEdgeLabel(TextBlock textblock)
        {
            if (!MainCanvas.Children.Contains(textblock))
            {
                MainCanvas.Children.Add(textblock);
                m_edgeLabels.Add(textblock);
                TransitionUtility.ApplyCreateLabelAnimation(textblock);
                return true;
            }
            return false;
        }

        #endregion

        #region Fields

        private Dictionary<Node, VisualNode> m_nodeToVisualNode =
             new Dictionary<Node, VisualNode>();

        private Dictionary<VisualNode, Dictionary<VisualNode, VisualEdge>> m_visualNodesToVisualEdge =
                new Dictionary<VisualNode, Dictionary<VisualNode, VisualEdge>>();

        private Dictionary<Node, Point> m_hiddenNodePositions = new Dictionary<Node, Point>();
        private List<TextBlock> m_edgeLabels = new List<TextBlock>();
        private List<VisualEdge> m_allEdges = new List<VisualEdge>();

        // scrolling fields
        private Point m_startScrollPoint;
        private Point m_startScrollOffset;

        private int m_zoomRequestCount = 0;


        #endregion
    }

    #region Internal helper classes



    /// <summary>
    /// An internal random class for easy random number access
    /// </summary>
    internal static class Rand
    {
        public static double Double
        {
            get { return rand.NextDouble(); }
        }
        public static int Int(int from, int to)
        {
            return rand.Next(from, to);
        }
        static Random rand = new Random();
    }

    #endregion
}
